home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / ISSUE09 / PERFORM / DIROUTLN.PAS
Pascal/Delphi Source File  |  1996-04-10  |  11KB  |  377 lines

  1. unit DirOutln;
  2.  
  3. { Directory outline component - optimised by Dr.Bob for The Delphi Magazine }
  4.  
  5. interface
  6.  
  7. uses Classes, Forms, Controls, Outline, SysUtils, Graphics, Grids, StdCtrls,
  8.      Menus;
  9.  
  10. type
  11.   TTextCase = (tcLowerCase, tcUpperCase, tcAsIs);
  12.   TCaseFunction = function(const AString: string): string;
  13.  
  14.   TDirectoryOutline = class(TCustomOutline)
  15.   private
  16.     FDrive: Char;
  17.     FDirectory: TFileName;
  18.     FOnChange: TNotifyEvent;
  19.     FTextCase: TTextCase;
  20.     FCaseFunction: TCaseFunction;
  21.   protected
  22.     procedure SetDrive(NewDrive: Char);
  23.     procedure SetDirectory(const NewDirectory: TFileName);
  24.     procedure SetTextCase(NewTextCase: TTextCase);
  25.     procedure AssignCaseProc;
  26.     procedure BuildOneLevel(RootItem: Longint); virtual;
  27.     procedure BuildTree; virtual;
  28.     procedure BuildSubTree(RootItem: Longint); virtual;
  29.     procedure Change; virtual;
  30.     procedure Click; override;
  31.     procedure CreateWnd; override;
  32.     procedure Expand(Index: Longint); override;
  33.     procedure Loaded; override;
  34.     procedure WalkTree(const Dest: string);
  35.   public
  36.     constructor Create(AOwner: TComponent); override;
  37.     function ForceCase(const AString: string): string;
  38.     property Drive: Char  read FDrive write SetDrive;
  39.     property Directory: TFileName  read FDirectory write SetDirectory;
  40.     property Lines stored False;
  41.   published
  42.     property Align;
  43.     property BorderStyle;
  44.     property Color;
  45.     property Ctl3D;
  46.     property DragCursor;
  47.     property DragMode;
  48.     property Enabled;
  49.     property Font;
  50.     property ItemHeight;
  51.     property OnChange: TNotifyEvent  read FOnChange write FOnChange;
  52.     property OnClick;
  53.     property OnCollapse;
  54.     property OnDblClick;
  55.     property OnDragDrop;
  56.     property OnDragOver;
  57.     property OnDrawItem;
  58.     property OnEndDrag;
  59.     property OnEnter;
  60.     property OnExit;
  61.     property OnExpand;
  62.     property OnKeyDown;
  63.     property OnKeyPress;
  64.     property OnKeyUp;
  65.     property OnMouseDown;
  66.     property OnMouseMove;
  67.     property OnMouseUp;
  68.     property Options default [ooStretchBitmaps, ooDrawFocusRect];
  69.     property ParentColor;
  70.     property ParentCtl3D;
  71.     property ParentFont;
  72.     property ParentShowHint;
  73.     property PictureClosed;
  74.     property PictureLeaf;
  75.     property PictureOpen;
  76.     property PopupMenu;
  77.     property ScrollBars;
  78.     property Style;
  79.     property ShowHint;
  80.     property TabOrder;
  81.     property TabStop;
  82.     property TextCase: TTextCase  read FTextCase write SetTextCase default tcLowerCase;
  83.     property Visible;
  84.   end;
  85.  
  86. function SameLetter(Letter1, Letter2: Char): Boolean;
  87.  
  88.  
  89. implementation
  90.  
  91.   {$IFNDEF WIN32} { Dr.Bob: for 16-bit compatibility }
  92.   procedure SetLength(var Str: String; Len: Integer);
  93.   begin
  94.     Str[0] := Chr(Len)
  95.   end {SetLength};
  96.   {$ENDIF}
  97.  
  98. const
  99.   InvalidIndex = -1;
  100.  
  101. constructor TDirectoryOutline.Create(AOwner: TComponent);
  102. begin
  103.   inherited Create(AOwner);
  104.   PictureLeaf := PictureClosed;
  105.   Options := [ooStretchBitmaps, ooDrawFocusRect];
  106.   TextCase := tcLowerCase;
  107.   AssignCaseProc;
  108. end;
  109.  
  110. procedure TDirectoryOutline.AssignCaseProc;
  111. begin
  112.   case TextCase of
  113.     tcLowerCase: FCaseFunction := AnsiLowerCase;
  114.     tcUpperCase: FCaseFunction := AnsiUpperCase;
  115.     else FCaseFunction := nil;
  116.   end;
  117. end;
  118.  
  119. type
  120.   PNodeInfo = ^TNodeInfo;
  121.   TNodeInfo = record
  122.     RootName: TFileName;
  123.     SearchRec: TSearchRec;
  124.     DosError: Integer;
  125.     RootNode: TOutlineNode;
  126.     TempChild, NewChild: Longint;
  127.   end;
  128.  
  129. procedure TDirectoryOutline.BuildOneLevel(RootItem: Longint);
  130. var
  131.   NodeInfo: PNodeInfo;
  132.  
  133.   function FindIndex(RootNode: TOutLineNode; SearchName: TFileName): LongInt;
  134.   { speed-up by Dr.Bob: use Binary Search! }
  135.   var FirstChild,LastChild,TempChild: LongInt;
  136.   begin
  137.     FirstChild := RootNode.GetFirstChild;
  138.     if (FirstChild = InvalidIndex) or
  139.        (SearchName <= Items[FirstChild].Text) then FindIndex := FirstChild
  140.     else
  141.     begin
  142.       LastChild := RootNode.GetLastChild;
  143.       if SearchName >= Items[LastChild].Text then FindIndex := InvalidIndex {!}
  144.       else
  145.       begin
  146.         repeat
  147.           TempChild := (FirstChild + LastChild) div 2; { binary search }
  148.           if TempChild = FirstChild then Inc(TempChild);
  149.           if SearchName > Items[TempChild].Text then FirstChild := TempChild
  150.                                                 else LastChild := TempChild
  151.         until FirstChild >= (LastChild-1);
  152.         FindIndex := LastChild
  153.       end
  154.     end
  155.   end {FindIndex};
  156.  
  157. begin
  158.   New(NodeInfo);
  159.   try
  160.     with NodeInfo^ do
  161.     begin
  162.       RootName := Items[RootItem].FullPath;
  163.       if RootName[Length(RootName)] <> '\' then
  164.         RootName := Concat(RootName, '\');
  165.       RootName := Concat(RootName, '*.*');
  166.       DosError := FindFirst(RootName, faDirectory, SearchRec);
  167.       while DosError = 0 do
  168.       begin
  169.         if (SearchRec.Attr and faDirectory = faDirectory) and (SearchRec.Name[1] <> '.') then
  170.         begin
  171.           SearchRec.Name := ForceCase(SearchRec.Name);
  172.           RootNode := Items[RootItem];
  173.           if RootNode.HasItems then { if has children, must alphabetize }
  174.           begin
  175.             TempChild := RootNode.GetFirstChild;
  176.             {$IFDEF ORIGINAL }{ Dr.Bob: bottle-neck - lineair search applied }
  177.             while (TempChild <> InvalidIndex) and (Items[TempChild].Text < SearchRec.Name) do
  178.               TempChild := RootNode.GetNextChild(TempChild);
  179.             {$ELSE}
  180.             TempChild := FindIndex(RootNode, SearchRec.Name); { Dr.Bob }
  181.             {$ENDIF}
  182.             if TempChild <> InvalidIndex then
  183.               NewChild := Insert(TempChild, SearchRec.Name)
  184.             else NewChild := Add(RootNode.GetLastChild, SearchRec.Name);
  185.           end
  186.           else NewChild := AddChild(RootItem, SearchRec.Name); { if first child, just add }
  187.         end;
  188.         DosError := FindNext(SearchRec);
  189.       end;
  190.     end;
  191.     Items[RootItem].Data := Pointer(1); { make non-nil so we know we've been here }
  192.   finally
  193.     Dispose(NodeInfo);
  194.   end;
  195. end;
  196.  
  197. procedure TDirectoryOutline.BuildTree;
  198. begin
  199.   Clear;
  200.   AddChild(0, ForceCase(Drive + ':'));
  201.   WalkTree(FDirectory);
  202.   Change;
  203. end;
  204.  
  205. procedure TDirectoryOutline.BuildSubTree(RootItem: Longint);
  206. var
  207.   TempRoot: Longint;
  208.   RootNode: TOutlineNode;
  209. begin
  210.   BuildOneLevel(RootItem);
  211.   RootNode := Items[RootItem];
  212.   TempRoot := RootNode.GetFirstChild;
  213.   while TempRoot <> InvalidIndex do
  214.   begin
  215.     BuildSubTree(TempRoot);
  216.     TempRoot := RootNode.GetNextChild(TempRoot);
  217.   end;
  218. end;
  219.  
  220. procedure TDirectoryOutline.Change;
  221. begin
  222.   if Assigned(FOnChange) then FOnChange(Self);
  223. end;
  224.  
  225. procedure TDirectoryOutline.Click;
  226. begin
  227.   inherited Click;
  228.   Directory := Items[SelectedItem].FullPath;
  229. end;
  230.  
  231. procedure TDirectoryOutline.CreateWnd;
  232. var
  233.   CurrentPath: string;
  234. begin
  235.   inherited CreateWnd;
  236.   if FDrive = #0 then
  237.   begin
  238.     GetDir(0, CurrentPath);
  239.     FDrive := ForceCase(CurrentPath)[1];
  240.     FDirectory := ForceCase(CurrentPath);
  241.   end;
  242.   if (not (csLoading in ComponentState)) and
  243.     (csDesigning in ComponentState) then BuildTree;
  244. end;
  245.  
  246. procedure TDirectoryOutline.Expand(Index: Longint);
  247. begin
  248.   if Items[Index].Data = nil then { if we've not previously expanded }
  249.     BuildOneLevel(Index);
  250.   inherited Expand(Index); { call the event handler }
  251. end;
  252.  
  253. function TDirectoryOutline.ForceCase(const AString: string): string;
  254. begin
  255.   if Assigned(FCaseFunction) then
  256.     Result := FCaseFunction(AString)
  257.   else Result := AString;
  258. end;
  259.  
  260. procedure TDirectoryOutline.Loaded;
  261. begin
  262.   inherited Loaded;
  263.   AssignCaseProc;
  264.   BuildTree;
  265. end;
  266.  
  267. procedure TDirectoryOutline.SetDirectory(const NewDirectory: TFileName);
  268. var
  269.   TempPath: TFileName;
  270. begin
  271.   if Length(NewDirectory) > 0 then  { ignore empty directory }
  272.   begin
  273.     TempPath := ForceCase(ExpandFileName(NewDirectory)); { expand to full path }
  274.     if (Length(TempPath) > 3) and (TempPath[Length(TempPath)] = '\') then
  275.       SetLength(TempPath, Length(TempPath) - 1);
  276.     if CompareStr(TempPath, FDirectory) <> 0 then { is it a dir change? }
  277.     begin
  278.       FDirectory := TempPath; { set new directory }
  279.       ChDir(FDirectory); { go there }
  280.       if TempPath[1] <> Drive then { check to see if we changed drives, too }
  281.         Drive := TempPath[1] { change drive/build list if needed }
  282.       else
  283.       begin
  284.         WalkTree(TempPath);
  285.         Change; { otherwise, we're done }
  286.       end;
  287.     end;
  288.   end;
  289. end;
  290.  
  291. procedure TDirectoryOutline.SetDrive(NewDrive: Char);
  292. var
  293.   TempPath: string;
  294. begin
  295.   if UpCase(NewDrive) in ['A'..'Z'] then { disallow all but drive letters}
  296.   begin
  297.     if (FDrive = #0) or not SameLetter(NewDrive, FDrive) then { update if no current drive or change }
  298.     begin
  299.       FDrive := NewDrive;
  300.       ChDir(FDrive + ':');
  301.       GetDir(0, TempPath);
  302.       FDirectory := ForceCase(TempPath); { use correct case }
  303.       if not (csLoading in ComponentState) then BuildTree; { this ends up calling Change }
  304.     end;
  305.   end;
  306. end;
  307.  
  308. procedure TDirectoryOutline.SetTextCase(NewTextCase: TTextCase);
  309. var
  310.   CurrentPath: string;
  311. begin
  312.   if NewTextCase <> FTextCase then
  313.   begin
  314.     FTextCase := NewTextCase;
  315.     AssignCaseProc;
  316.     if NewTextCase = tcAsIs then
  317.     begin
  318.       GetDir(0, CurrentPath);
  319.       FDrive := CurrentPath[1];
  320.       FDirectory := CurrentPath;
  321.     end;
  322.     if not (csLoading in ComponentState) then BuildTree;
  323.   end;
  324. end;
  325.  
  326. procedure TDirectoryOutline.WalkTree(const Dest: string);
  327. var
  328.   TempPath, NextDir: TFileName;
  329.   SlashPos: Integer;
  330.   TempItem: Longint;
  331.  
  332.   function GetChildNamed(const Name: string): Longint;
  333.   begin
  334.     Items[TempItem].Expanded := True;
  335.     Result := Items[TempItem].GetFirstChild;
  336.     while Result <> InvalidIndex do
  337.     begin
  338.       if Items[Result].Text = Name then Exit;
  339.       Result := Items[TempItem].GetNextChild(Result);
  340.     end;
  341.   end;
  342.  
  343. begin
  344.   TempItem := 1; { start at root }
  345.   TempPath := ForceCase(Dest);
  346.   if Pos(':', TempPath) > 0 then
  347.     TempPath := Copy(TempPath, Pos(':', TempPath) + 1, Length(TempPath));
  348.   if TempPath[1] = '\' then System.Delete(TempPath, 1, 1);
  349.   {$IFDEF WIN32}
  350.   Pos('\', TempPath); { Dr.Bob: what is the meaning of this statement?? }
  351.   {$ENDIF}
  352.   NextDir := TempPath;
  353.   while Length(TempPath) > 0 do
  354.   begin
  355.     SlashPos := Pos('\', TempPath);
  356.     if SlashPos > 0 then
  357.     begin
  358.       NextDir := Copy(TempPath, 1, SlashPos - 1);
  359.       TempPath := Copy(TempPath, SlashPos + 1, Length(TempPath));
  360.     end
  361.     else
  362.     begin
  363.       NextDir := TempPath;
  364.       TempPath := '';
  365.     end;
  366.     TempItem := GetChildNamed(NextDir);
  367.   end;
  368.   SelectedItem := TempItem;
  369. end;
  370.  
  371. function SameLetter(Letter1, Letter2: Char): Boolean;
  372. begin
  373.   Result := UpCase(Letter1) = UpCase(Letter2);
  374. end;
  375.  
  376. end.
  377.